package namespaces

import (
	"fmt"
	"time"

	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/binocle"
	"gddgame.cc/galaxy/binocle/module/report"
	"gddgame.cc/galaxy/core/models"
	"gddgame.cc/galaxy/core/sdk"
	"gddgame.cc/galaxy/satellite/formula/nebula"
	"gddgame.cc/galaxy/server/composer"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/calendar"
	"gddgame.cc/galaxy/utils/def"
)

//
const (
	LocationEvent nebula.NSE = iota
	VersionEvent
	AuthEvent
	LoginEvent
	ChangeNickNameEvent
	GuestEvent
	InviteFriend
	ConfirmFriend

	OauthEvent
	AppOauthEvent
	AppOfflineOauthEvent
	StatEvent
	FeedbackEvent
	OauthListEvent

	GetPreferenceEvent
	UpdatePreferenceEvent

	GetRealStatus
	SetRealInfo

	OnInvitedFriend   nebula.NSE = 126
	OnConfirmedFriend nebula.NSE = 127
)

type AuthManager interface {
	Depends() error
}

type AuthDelegate interface {
	Version() string
	GetUserId(userOpen *models.UserOpen) (int, error)
	Connect(context def.Context, userOpen *models.UserOpen, userId int)
	SendSharedMessage(userOpen *models.UserOpen, userId int, origin string) error
	Disconnect(context def.Context, openId string, userId int)
	GetCache(openId string, key string, value interface{}) error
	SetCache(openId string, key string, value interface{}, expire int) error
}

type authService struct {
	builder composer.Builder
	binocle.App
	Sdk *sdk.AppClient
}

func (service *authService) Depends() error {
	return nil
}
func (service *authService) bindEvent(agent binocle.App, builder composer.Builder, socket *nebula.Socket, openId string) {
	single := agent.GetUserRoom(socket, openId)
	single.Filter("friend/invited", func(message asteroid.Message, member def.Member) {
		//ss := member.(*nebula.Socket)
		userId := message.Data().(string)
		builder.Debugf("friend/invited: %s", userId)
		if socket.ExitStatus != nebula.NoneExit {
			return
		}
		_ = socket.Emit(OnInvitedFriend, userId)
	})
	single.Filter("friend/confirmed", func(message asteroid.Message, member def.Member) {
		//ss := member.(*nebula.Socket)
		userId := message.Data().(string)
		builder.Debugf("friend/confirmed: %s", userId)
		if socket.ExitStatus != nebula.NoneExit {
			return
		}
		_ = socket.Emit(OnConfirmedFriend, userId)
	})
}

/*
 授权操作，在根结点
*/
func AuthHandler(builder composer.Builder, namespace nebula.Namespace, client *sdk.AppClient, delegate AuthDelegate) AuthManager {
	var agent binocle.App
	builder.Factory("agent", &agent)

	authManager := &authService{builder: builder, App: agent}
	_ = builder.BindInstance("auth", authManager)

	loginStat := agent.AddMetrics().Period(report.Minute1).Type(report.PersistenceMetric).
		Aggregate(report.Sum, report.Sum).
		Reporter()
	loginStat.Register("login.number", "login.guest",
		"wechat_app.number", "wechat_app.new",
		"qq_app.number", "qq_app.new",
		"facebook_app.number", "facebook_app.new",
		"apple_game_center.number", "apple_game_center.new",
		"google_game_play.number", "google_game_play.new")

	eventStats := agent.AddMetrics().Period(report.Minute1).Type(report.PersistenceMetric).
		Aggregate(report.Sum, report.Sum).Reporter()

	//var app *models.App
	_ = namespace.Connect(func(socket *nebula.Socket, next nebula.Next) {
		var err error
		appData := socket.App()
		if appData == nil {
			socket.Close()
			return
		}
		_, err = client.ValidAppToken(appData.Id, appData.Token)
		if err != nil {
			socket.Close()
			return
		}
		if err := next(nil); err != nil {
			socket.Close()
			return
		}
		// 存储登录时间
		_ = socket.Set("loginTime", time.Now().Unix())
		// todo 验证app版本
	})

	_ = namespace.Disconnect(func(socket *nebula.Socket, reason string) {
		var openId string
		var userId int
		if err := socket.Get("open_id", &openId); err == nil {
			agent.Leave(socket, openId)
		}
		if err := socket.Get("user_id", &userId); err != nil {
			return
		}
		delegate.Disconnect(socket, openId, userId)

		// 计算防沉迷
		if userId <= 0 {
			return
		}
		var loginTime int64
		var realExpireTime int64

		if err := socket.Get("loginTime", &loginTime); err != nil {
			return
		}

		if err := socket.Get("realExpireTime", &realExpireTime); err != nil {
			return
		}
		currentReal := 0
		t := time.Now().Unix()
		_ = delegate.GetCache("currentReal", openId, &currentReal)
		_ = delegate.SetCache("currentReal", openId, currentReal+int(t-loginTime), int(realExpireTime-t))
	})

	_ = namespace.On(LocationEvent, func(req *nebula.Request, language string, zone int) {
		// 验证多语言类型
		_ = req.Set("language", language)
		// 验证时区
		_ = req.Set("zone", time.FixedZone("UTC"+string(zone), zone*3600))
	})

	_ = namespace.On(VersionEvent, func(req *nebula.Request, zone int) {
		req.Response(delegate.Version())

	})

	_ = namespace.On(AuthEvent, func(req *nebula.Request, token string) {
		// 验证账号
		var err error
		userOpen, err := client.ValidToken(token)
		if err != nil {
			req.ResponseErr(err)
			return
		}
		userId, err := delegate.GetUserId(userOpen)
		if err != nil {
			return
		}
		_ = req.Set("user_id", userId)
		_ = req.Set("open_id", userOpen.Id)
		delegate.Connect(req, userOpen, userId)

		agent.Join(req, userOpen.Id)
		loginStat.Increase("login.number")
		req.Response(userOpen.GetInfo())

		authManager.bindEvent(agent, builder, req.Socket, userOpen.Id)
	})

	_ = namespace.On(LoginEvent, func(req *nebula.Request, username string, password string) {
		// 验证账号
		userOpen, err := client.Login(username, password)
		if err != nil {
			req.ResponseErr(err)
			return
		}
		userId, err := delegate.GetUserId(userOpen)
		if err != nil {
			return
		}
		_ = req.Set("user_id", userId)
		_ = req.Set("open_id", userOpen.Id)
		delegate.Connect(req, userOpen, userId)

		agent.Join(req, userOpen.Id)
		loginStat.Increase("login.number")
		req.Response(userOpen.GetInfo())

		authManager.bindEvent(agent, builder, req.Socket, userOpen.Id)
	})

	_ = namespace.On(ChangeNickNameEvent, func(req *nebula.Request, nickName string, avatar string) {
		var openId string
		if err := req.Get("open_id", &openId); err != nil {
			req.Response(false)
			return
		}
		if err := client.ChangeOpen(openId, nickName, avatar); err != nil {
			req.Response(false)
			return
		}
		req.Response(true)
	})

	_ = namespace.On(GuestEvent, func(req *nebula.Request) {
		userOpen, err := client.RegisterGuest()
		if err != nil {
			req.ResponseErr(err)
			return
		}
		userId, err := delegate.GetUserId(userOpen)
		if err != nil {
			return
		}
		_ = req.Set("user_id", userId)
		_ = req.Set("open_id", userOpen.Id)
		delegate.Connect(req, userOpen, userId)

		agent.Join(req, userOpen.Id)
		loginStat.Increase("login.guest")
		req.Response(userOpen.GetInfo())

		authManager.bindEvent(agent, builder, req.Socket, userOpen.Id)
	})

	_ = namespace.On(InviteFriend, func(req *nebula.Request, targetId string) {
		var openId string
		//var userId int
		if err := req.Get("open_id", &openId); err != nil {
			return
		}
		//if err := req.Get("user_id", &userId); err != nil {
		//	return
		//}
		if err := client.InviteFriend(openId, targetId); err != nil {
			req.ResponseErr(err)
			return
		}
		agent.Notice(targetId, "friend/invited", openId)
		req.Response(true)
		//userOpen, err := client.GetOpen(openId)
		//if err != nil {
		//	return
		//}
		//if err := delegate.SendFriendMessage(req, userOpen, userId, targetId); err != nil {
		//	return
		//}
	})

	_ = namespace.On(ConfirmFriend, func(req *nebula.Request, targetId string, ok bool) {
		var openId string
		if err := req.Get("open_id", &openId); err != nil {
			return
		}
		if err := client.ConfirmFriend(openId, targetId, ok); err != nil {
			builder.Println(err)
			req.ResponseErr(err)
			return
		}
		agent.Notice(targetId, "friend/confirmed", openId)
		req.Response(true)
	})

	_ = namespace.On(OauthEvent, func(req *nebula.Request, channel string, code string, state string) {
		var userOpenId string
		if err := req.Get("open_id", &userOpenId); err != nil {
			userOpenId = ""
		}
		userOpen, err := client.Oauth(channel, code, state, userOpenId)
		if err != nil {
			req.ResponseErr(err)
			return
		}
		if userOpenId != "" {
			// 已经登录，绑定该渠道
			req.Response(userOpen.GetInfo())
			return
		}
		userId, err := delegate.GetUserId(userOpen)
		if err != nil {
			req.ResponseErr(err)
			return
		}
		_ = req.Set("user_id", userId)
		_ = req.Set("open_id", userOpen.Id)
		delegate.Connect(req, userOpen, userId)

		loginStat.Increase(channel + ".number")
		if userOpen.LastTime == nil && userOpen.OriginOpenId != "" {
			// 第一次登录，并且有来源分享，发送分享成功消息
			if err := delegate.SendSharedMessage(userOpen, userId, userOpen.OriginOpenId); err != nil {
				builder.Error(err)
			}
			loginStat.Increase(channel + ".new")
		}
		agent.Join(req, userOpen.Id)
		req.Response(userOpen.GetInfo())

		authManager.bindEvent(agent, builder, req.Socket, userOpen.Id)
	})
	_ = namespace.On(AppOauthEvent, func(req *nebula.Request, channel string, openId string, accessToken string) {
		var userOpenId string
		fmt.Println(channel, openId, accessToken, userOpenId)
		if err := req.Get("open_id", &userOpenId); err != nil {
			userOpenId = ""
		}
		userOpen, err := client.AppOauth(channel, openId, accessToken, userOpenId)
		if err != nil {
			req.ResponseErr(err)
			return
		}
		if userOpenId != "" {
			// 已经登录，绑定该渠道
			req.Response(userOpen.GetInfo())
			return
		}
		userId, err := delegate.GetUserId(userOpen)
		if err != nil {
			req.ResponseErr(err)
			return
		}
		_ = req.Set("user_id", userId)
		_ = req.Set("open_id", userOpen.Id)
		delegate.Connect(req, userOpen, userId)

		loginStat.Increase(channel + ".number")
		if userOpen.LastTime == nil && userOpen.OriginOpenId != "" {
			// 第一次登录，并且有来源分享，发送分享成功消息
			if err := delegate.SendSharedMessage(userOpen, userId, userOpen.OriginOpenId); err != nil {
				builder.Error(err)
			}
			loginStat.Increase(channel + ".new")
		}
		agent.Join(req, userOpen.Id)
		req.Response(userOpen.GetInfo())

		authManager.bindEvent(agent, builder, req.Socket, userOpen.Id)
	})

	_ = namespace.On(AppOfflineOauthEvent, func(req *nebula.Request, channel string, openId string, nickname string) {
		var userOpenId string
		//fmt.Println(channel, openId, nickname, userOpenId)
		if err := req.Get("open_id", &userOpenId); err != nil {
			userOpenId = ""
		}
		userOpen, err := client.AppOfflineOauth(channel, openId, nickname, userOpenId)
		if err != nil {
			req.ResponseErr(err)
			return
		}
		if userOpenId != "" {
			// 已经登录，绑定该渠道
			req.Response(userOpen.GetInfo())
			return
		}
		userId, err := delegate.GetUserId(userOpen)
		if err != nil {
			req.ResponseErr(err)
			return
		}
		_ = req.Set("user_id", userId)
		_ = req.Set("open_id", userOpen.Id)
		delegate.Connect(req, userOpen, userId)

		loginStat.Increase(channel + ".number")
		if userOpen.LastTime == nil && userOpen.OriginOpenId != "" {
			// 第一次登录，并且有来源分享，发送分享成功消息
			if err := delegate.SendSharedMessage(userOpen, userId, userOpen.OriginOpenId); err != nil {
				builder.Error(err)
			}
			loginStat.Increase(channel + ".new")
		}
		agent.Join(req, userOpen.Id)
		req.Response(userOpen.GetInfo())

		authManager.bindEvent(agent, builder, req.Socket, userOpen.Id)
	})
	_ = namespace.On(StatEvent, func(req *nebula.Request, event string) {
		eventStats.Increase(event)
	})
	_ = namespace.On(FeedbackEvent, func(req *nebula.Request, message string) {
		var userOpenId string
		if err := req.Get("open_id", &userOpenId); err != nil {
			userOpenId = ""
		}
		if err := client.SubmitFeedback(userOpenId, message); err != nil {
			req.ResponseErr(err)
			return
		}
		req.Response(true)
	})
	_ = namespace.On(OauthListEvent, func(req *nebula.Request) {
		var openId string
		if err := req.Get("open_id", &openId); err != nil {
			req.Response(false)
			return
		}
		list, err := client.OauthList(openId)
		if err != nil {
			req.Response(false)
			return
		}
		req.Response(list)
	})
	_ = namespace.On(GetPreferenceEvent, func(req *nebula.Request) {
		var openId string
		if err := req.Get("open_id", &openId); err != nil {
			req.Response(nil)
			return
		}
		userOpen, err := client.GetOpen(openId)
		if err != nil {
			req.Response(nil)
			return
		}
		p := make([]string, len(userOpen.Preferences)*2)
		i := 0
		for k, v := range userOpen.Preferences {
			p[i] = k
			p[i+1] = v
			i += 2
		}
		req.Response(p)
	})
	_ = namespace.On(UpdatePreferenceEvent, func(req *nebula.Request, value []string) {
		var openId string
		if err := req.Get("open_id", &openId); err != nil {
			req.Response(false)
			return
		}
		userOpen, err := client.GetOpen(openId)
		if err != nil {
			req.Response(nil)
			return
		}
		for i := 0; i < len(value); i += 2 {
			userOpen.Preferences[value[i]] = value[i+1]
		}
		if err := client.UpdateOpenPreferences(openId, userOpen.Preferences); err != nil {
			req.Response(false)
			return
		}
		req.Response(true)
	})
	_ = namespace.On(GetRealStatus, func(req *nebula.Request) {
		var openId string
		if err := req.Get("open_id", &openId); err != nil {
			req.Response(false)
			return
		}

		userOpen, err := client.GetOpen(openId)
		if err != nil {
			req.Response(nil)
			return
		}

		user, err := client.GetUser(userOpen.UserId)
		if err != nil {
			req.Response(nil)
			return
		}
		needReal := true
		isUnderAge := true
		duration := 0
		info := utils.NewIDCard(user.IdCard)
		if (info == nil && !user.ThirdRealStatus) || (info != nil && info.Age == 0) {
			// 游客模式
			//在线时长不能超过一小时，15天内对同一设备永固不得重复提供游客体验
			currentReal := 0
			realExpireTime := int64(0)
			_ = delegate.GetCache(openId, "realExpireTime", &realExpireTime)
			if realExpireTime == 0 || realExpireTime < time.Now().Unix() {
				realExpireTime = time.Now().AddDate(0, 0, 15).Unix()
				_ = delegate.SetCache(openId, "realExpireTime", realExpireTime, 15*24*86400)
			}
			_ = req.Set("realExpireTime", realExpireTime)
			_ = delegate.GetCache("currentReal", openId, &currentReal)

			duration = int(3600) - currentReal
		} else if (user.ThirdRealStatus && !user.ThirdIsUnderAge) || info.Age >= 18 {
			needReal = false
			isUnderAge = false
		} else {
			needReal = false
			var zone *time.Location
			// 判断防沉迷时限
			if err := req.Get("zone", &zone); err != nil {
				zone, _ = time.LoadLocation("Local")
			}
			t := time.Now().In(zone)
			if t.Hour() < 8 || t.Hour() > 22 {
				duration = 0
			} else {
				all := 3 * 86400 / 2
				if calendar.IsHoliday(&t) {
					all *= 2
				}
				currentReal := 0
				_ = delegate.GetCache("currentReal", openId, &currentReal)
				duration = int(all) - currentReal

				today := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, zone)
				tommorrow := today.Add(24 * time.Hour)
				_ = req.Set("realExpireTime", tommorrow.Unix())
			}
			//每日22时至次日8时，不能游戏
			//每日在线时长不得超过1.5小时
			//法定节假日每日在线时长不得超过3小时
			//未满8岁不能付费
			//满8-16岁，每日单词付费不超过50元，每月累计付费不超过200元
			//满16-18岁，每日单词付费不超过100元，每月累计付费不超过400元
		}

		if duration < 0 {
			duration = 0
		}
		req.Response(needReal, isUnderAge, duration)
	})
	_ = namespace.On(SetRealInfo, func(req *nebula.Request, idCard string, realName string) {
		var openId string
		if err := req.Get("open_id", &openId); err != nil {
			req.Response(false)
			return
		}
		info := utils.NewIDCard(idCard)
		if info == nil || info.Age == 0 {
			req.Response(false)
			return
		}
		err := client.SetRealInfo(openId, idCard, realName)
		if err != nil {
			req.Response(false)
		} else {
			req.Response(true)
		}
	})
	return authManager
}
